package com.gigi.datageniebackendmaster.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gigi.datageniebackendmaster.common.*;
import com.gigi.datageniebackendmaster.config.TrackedTask;
import com.gigi.datageniebackendmaster.mapper.ChartMapper;
import com.gigi.datageniebackendmaster.model.entity.ChartEntity;
import com.gigi.datageniebackendmaster.model.entity.UserEntity;
import com.gigi.datageniebackendmaster.model.enums.ChartStatusEnum;
import com.gigi.datageniebackendmaster.model.enums.ErrorCodeEnum;
import com.gigi.datageniebackendmaster.model.request.GenChartRequest;
import com.gigi.datageniebackendmaster.model.request.ListChartRequest;
import com.gigi.datageniebackendmaster.model.vo.GenChartVO;
import com.gigi.datageniebackendmaster.model.vo.MyChartVO;
import com.gigi.datageniebackendmaster.mq.BiMessageProducer;
import com.gigi.datageniebackendmaster.service.ChartService;
import com.gigi.datageniebackendmaster.util.AuthorityUtil;
import com.gigi.datageniebackendmaster.util.ExcelUtil;
import com.gigi.datageniebackendmaster.util.ResultUtil;
import com.gigi.datageniebackendmaster.util.ThrowUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * <p>
 * 图表 服务实现类
 * </p>
 *
 * @author Gigi
 * @since 2024-07-21
 */
@Service
public class ChartServiceImpl extends ServiceImpl<ChartMapper, ChartEntity> implements ChartService {

    @Resource
    private AiManager aiManager;

    @Autowired(required = false)
    private RedisLimiterManager redisLimiterManager;

    @Resource
    @Lazy
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private BiMessageProducer biMessageProducer;

    @Override
    public BaseResponse<Boolean> genChartAsyncMq(GenChartRequest request, MultipartFile file, HttpServletRequest httpServletRequest) throws IOException {

        // 1、是否登录
        UserEntity loginUser = AuthorityUtil.getLoginUser(httpServletRequest);

        // 加限流器
        if (Objects.nonNull(redisLimiterManager)) {
            redisLimiterManager.doRateLimit("genChart_" + loginUser.getId());
        }

        // 2、分析诉求长度校验
        ThrowUtil.throwIf(request.getGoal().length() > 200, ErrorCodeEnum.PARAMS_ERROR, "分析诉求长度不能超过200字");

        // 3、文件合法校验
        ThrowUtil.throwIf(Objects.isNull(file), ErrorCodeEnum.PARAMS_ERROR, "原始数据不能为空");
        ThrowUtil.throwIf(file.getSize() > 1024 * 1024 * 10, ErrorCodeEnum.PARAMS_ERROR, "原始数据大小不能超过10M");
        final List<String> validSuffixes = Arrays.asList("xlsx", "xls");
        ThrowUtil.throwIf(!validSuffixes.contains(FileUtil.getSuffix(file.getOriginalFilename())),
                ErrorCodeEnum.PARAMS_ERROR, "原始数据类型不正确");

        // 4、压缩原始数据
        String rawData = ExcelUtil.excelToCsv(file);

        // 5、先保存到数据库，状态为 “等待中”
        ChartEntity chartEntity = new ChartEntity();
        chartEntity.setUserId(loginUser.getId());
        chartEntity.setGoal(request.getGoal());
        chartEntity.setType(request.getType());
        chartEntity.setRawData(rawData);
        chartEntity.setChartName(request.getChartName());
        ThrowUtil.throwIf(!this.save(chartEntity), ErrorCodeEnum.OPERATION_ERROR, "保存图表失败");

        // 6、生产者发消息，消息内容为 chartId
        biMessageProducer.sendMessage(String.valueOf(chartEntity.getId()));

        return ResultUtil.success(true);
    }

    @Override
    public BaseResponse<Boolean> genChartAsync(GenChartRequest request, MultipartFile file, HttpServletRequest httpServletRequest) throws IOException {

        // 1、是否登录
        UserEntity loginUser = AuthorityUtil.getLoginUser(httpServletRequest);

        // 加限流器
        if (Objects.nonNull(redisLimiterManager)) {
            redisLimiterManager.doRateLimit("genChart_" + loginUser.getId());
        }

        // 2、分析诉求长度校验
        ThrowUtil.throwIf(request.getGoal().length() > 200, ErrorCodeEnum.PARAMS_ERROR, "分析诉求长度不能超过200字");

        // 3、文件合法校验
        ThrowUtil.throwIf(Objects.isNull(file), ErrorCodeEnum.PARAMS_ERROR, "原始数据不能为空");
        ThrowUtil.throwIf(file.getSize() > 1024 * 1024 * 10, ErrorCodeEnum.PARAMS_ERROR, "原始数据大小不能超过10M");
        final List<String> validSuffixes = Arrays.asList("xlsx", "xls");
        ThrowUtil.throwIf(!validSuffixes.contains(FileUtil.getSuffix(file.getOriginalFilename())),
                ErrorCodeEnum.PARAMS_ERROR, "原始数据类型不正确");

        // 4、压缩原始数据
        String rawData = ExcelUtil.excelToCsv(file);

        // 5、构造提词
        String goal = request.getGoal();
        StringBuffer userInput = new StringBuffer();
        if (StrUtil.isNotBlank(request.getType())) {
            goal += ",用" + request.getType();
        }
        userInput.append("分析诉求:").append(StrUtil.LF).append(goal).append(StrUtil.LF);
        userInput.append("原始数据:").append(StrUtil.LF).append(rawData);

        // 6、先保存到数据库，状态为 “等待中”
        ChartEntity chartEntity = new ChartEntity();
        chartEntity.setUserId(loginUser.getId());
        chartEntity.setGoal(request.getGoal());
        chartEntity.setType(request.getType());
        chartEntity.setRawData(rawData);
        chartEntity.setChartName(request.getChartName());
        ThrowUtil.throwIf(!this.save(chartEntity), ErrorCodeEnum.OPERATION_ERROR, "保存图表失败");

        // 7、提交调 AI 任务
        TrackedTask trackedTask = new TrackedTask(() -> {

            // 先将数据库状态改为 “执行中”
            ChartEntity target = new ChartEntity();
            target.setId(chartEntity.getId());
            target.setStatus(ChartStatusEnum.RUNNING.getCode());
            if (!this.updateById(target)) {
                handleUpdateError(chartEntity.getId(), "更新图表执行中状态失败");
            }

            // 调AI
            String result = aiManager.doChat(Constant.BI_MODEL_ID, userInput.toString());

            // 处理结果
            String[] splits = result.split("【【【【");
            if (splits.length < 3) {
                handleUpdateError(chartEntity.getId(), "AI 生成格式错误");
            }
            String resultChart = splits[1].trim();
            String resultText = splits[2].trim();

            // 更新数据库
            ChartEntity updateTarget = new ChartEntity();
            updateTarget.setId(chartEntity.getId());
            updateTarget.setResultChart(resultChart);
            updateTarget.setResultText(resultText);
            updateTarget.setStatus(ChartStatusEnum.SUCCEED.getCode());
            if (!this.updateById(updateTarget)) {
                handleUpdateError(chartEntity.getId(), "更新图表成功状态失败");
            }
        }, chartEntity.getId());
        threadPoolExecutor.execute(trackedTask);

        // 9、返回
        return ResultUtil.success(true);
    }

    @Override
    public void handleUpdateError(Long id, String errorMsg) {
        ChartEntity entity = new ChartEntity();
        entity.setId(id);
        entity.setStatus(ChartStatusEnum.FAILED.getCode());
        entity.setGenLog(errorMsg);
        ThrowUtil.throwIf(!this.updateById(entity), ErrorCodeEnum.OPERATION_ERROR, "更新图表失败状态失败");
    }

    @Override
    public BaseResponse<GenChartVO> genChart(GenChartRequest request, MultipartFile file, HttpServletRequest httpServletRequest) throws IOException {

        // 1、是否登录
        UserEntity loginUser = AuthorityUtil.getLoginUser(httpServletRequest);

        // 加限流器
        if (Objects.nonNull(redisLimiterManager)) {
            redisLimiterManager.doRateLimit("genChart_" + loginUser.getId());
        }

        // 2、分析诉求长度校验
        ThrowUtil.throwIf(request.getGoal().length() > 200, ErrorCodeEnum.PARAMS_ERROR, "分析诉求长度不能超过200字");

        // 3、文件合法校验
        ThrowUtil.throwIf(Objects.isNull(file), ErrorCodeEnum.PARAMS_ERROR, "原始数据不能为空");
        ThrowUtil.throwIf(file.getSize() > 1024 * 1024 * 10, ErrorCodeEnum.PARAMS_ERROR, "原始数据大小不能超过10M");
        final List<String> validSuffixes = Arrays.asList("xlsx", "xls");
        ThrowUtil.throwIf(!validSuffixes.contains(FileUtil.getSuffix(file.getOriginalFilename())),
                ErrorCodeEnum.PARAMS_ERROR, "原始数据类型不正确");

        // 4、压缩原始数据
        String rawData = ExcelUtil.excelToCsv(file);

        // 5、构造提词
        String goal = request.getGoal();
        StringBuffer userInput = new StringBuffer();
        if (StrUtil.isNotBlank(request.getType())) {
            goal += ",用" + request.getType();
        }
        userInput.append("分析诉求:").append(StrUtil.LF).append(goal).append(StrUtil.LF);
        userInput.append("原始数据:").append(StrUtil.LF).append(rawData);

        // 6、调AI
        String result = aiManager.doChat(Constant.BI_MODEL_ID, userInput.toString());

        // 7、处理结果
        String[] splits = result.split("【【【【");
        ThrowUtil.throwIf(splits.length < 3, ErrorCodeEnum.OPERATION_ERROR, "AI 生成格式错误");
        String resultChart = splits[1].trim();
        String resultText = splits[2].trim();

        // 8、保存到数据库
        ChartEntity chartEntity = new ChartEntity();
        chartEntity.setUserId(loginUser.getId());
        chartEntity.setGoal(request.getGoal());
        chartEntity.setType(request.getType());
        chartEntity.setRawData(rawData);
        chartEntity.setChartName(request.getChartName());
        chartEntity.setResultChart(resultChart);
        chartEntity.setResultText(resultText);
        chartEntity.setStatus(ChartStatusEnum.SUCCEED.getCode());
        ThrowUtil.throwIf(!this.save(chartEntity), ErrorCodeEnum.OPERATION_ERROR, "保存图表失败");

        // 9、返回
        GenChartVO genChartVO = new GenChartVO();
        genChartVO.setResultChart(resultChart);
        genChartVO.setResultText(resultText);
        return ResultUtil.success(genChartVO);
    }

    @Override
    public BaseResponse<MyChartVO> list(ListChartRequest request, HttpServletRequest httpServletRequest) {
        // 1、是否登录
        UserEntity loginUser = AuthorityUtil.getLoginUser(httpServletRequest);

        // 2、查询
        LambdaQueryWrapper<ChartEntity> wrapper = new LambdaQueryWrapper<ChartEntity>()
                .eq(ChartEntity::getUserId, loginUser.getId())
                .like(StrUtil.isNotBlank(request.getChartName()), ChartEntity::getChartName, request.getChartName())
                .ne(ChartEntity::getStatus, ChartStatusEnum.REJECTED.getCode())
                .orderByDesc(ChartEntity::getId);
        Page<ChartEntity> page = this.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper);

        // 3、返回
        if (Objects.isNull(page) || page.getTotal() == 0) {
            return ResultUtil.success(new MyChartVO(Collections.emptyList(), 0));
        }
        List<GenChartVO> list = page.getRecords().stream().map(e -> {
            GenChartVO genChartVO = new GenChartVO();
            BeanUtil.copyProperties(e, genChartVO);
            genChartVO.setId(e.getId().toString());
            genChartVO.setCreateTime(LocalDateTimeUtil.format(e.getCreateTime(),
                    DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
            return genChartVO;
        }).collect(Collectors.toList());
        return ResultUtil.success(new MyChartVO(list, page.getTotal()));
    }

    @Override
    public BaseResponse<Boolean> del(IdRequest request, HttpServletRequest httpServletRequest) {
        // 1、图表是否存在
        ChartEntity entity = this.getById(request.getId());
        ThrowUtil.throwIf(Objects.isNull(entity), ErrorCodeEnum.NOT_FOUND_ERROR);
        // 2、是否是本人或管理员
        AuthorityUtil.isAdminOrSelf(httpServletRequest, entity.getUserId());
        // 3、删除
        ThrowUtil.throwIf(!this.removeById(request.getId()), ErrorCodeEnum.OPERATION_ERROR);
        return ResultUtil.success(true);
    }

}
